﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Ignitron.EEngine.Adapters.DX.System;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using Buffer = SharpDX.Direct3D11.Buffer;
using Device = SharpDX.Direct3D11.Device;

namespace Ignitron.EEngine.Adapters.DX.Graphics
{
    /// <summary>
    /// Abstract shader base class
    /// </summary>
    internal abstract class ShaderBase
    {
        #region Structures and Buffers
        
        /// <summary>
        /// Matrix structure
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct MatrixBuffer
        {
            public Matrix world;
            public Matrix view;
            public Matrix projection;
        }

        /// <summary>
        /// Vertex and pixel shader
        /// </summary>
        protected VertexShader VertexShader { get; set; }
        protected PixelShader PixelShader { get; set; }


        /// <summary>
        /// Shader buffers and other variables
        /// </summary>
        protected InputLayout Layout { get; set; }
        protected SamplerState SampleState { get; set; }
        protected Buffer ConstantMatrixBuffer { get; set; }

        #endregion


        /// <summary>
        /// Device and window parameters
        /// </summary>
        protected Device mDevice;
        protected IntPtr mWindowHandle;

        /// <summary>
        /// Transformation matrices
        /// </summary>
        protected Matrix mWorldMatrix;
        protected Matrix mViewMatrix;
        protected Matrix mProjectionMatrix;

        #region Public API

        /// <summary>
        /// General shader initialization method
        /// </summary>
        public bool Initialize(Device device, IntPtr windowHandler, string vertexShader, string pixelShader)
        {
            mDevice = device;
            mWindowHandle = windowHandler;

			// Initialize the vertex and pixel shaders.
			return InitializeShader(device, windowHandler, vertexShader, pixelShader);           
        }

        /// <summary>
        /// General shader shutdown method
        /// </summary>
        public void Shuddown()
        {
            ShuddownShader();
        }

        /// <summary>
        /// Sets the transformation matrices
        /// </summary>
        public void SetTransformMatrices(Matrix world, Matrix view, Matrix projection)
        {
            mWorldMatrix = world;
            mViewMatrix = view;
            mProjectionMatrix = projection;
        }

        #endregion

        #region Initialization and Shutdown


        /// <summary>
        /// Abstract method used to initialize the shader
        /// </summary>
        protected abstract bool InitializeShader(Device device, IntPtr windowHandler, string vsFileName, string psFileName);

        /// <summary>
        /// Creates the structure of input elements for the shader
        /// </summary>
        protected abstract InputElement[] CreateInputElements();

        /// <summary>
        /// Method loading and compiling vertex and pixel shader
        /// </summary>
        protected void CompileShaders(Device device, IntPtr windowHandler, string vsFileName, string psFileName, string vsEntryPoint, string psEntryPoint)
        {
            try
            {
                // Setup full pathes
                vsFileName = SystemConfiguration.ShadersFilePath + vsFileName;
                psFileName = SystemConfiguration.ShadersFilePath + psFileName;

                // load the shaders from resource
                string vsContent, psContent;

                var assembly = Assembly.GetExecutingAssembly();

                // load vertex shader from the resources
                using (Stream stream = assembly.GetManifestResourceStream(vsFileName))
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        vsContent = reader.ReadToEnd();
                    }
                }

                // load pixel shader from the resources
                using (Stream stream = assembly.GetManifestResourceStream(psFileName))
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        psContent = reader.ReadToEnd();
                    }
                }

                // Compile the vertex shader code.
                var vertexShaderByteCode = ShaderBytecode.Compile(vsContent, vsEntryPoint, SystemConfiguration.VertexShaderProfile, ShaderFlags.None, EffectFlags.None);
                // Compile the pixel shader code.
                var pixelShaderByteCode = ShaderBytecode.Compile(psContent, psEntryPoint, SystemConfiguration.PixelShaderProfile, ShaderFlags.None, EffectFlags.None);

                // Create the vertex shader from the buffer.
                VertexShader = new VertexShader(device, vertexShaderByteCode);
                // Create the pixel shader from the buffer.
                PixelShader = new PixelShader(device, pixelShaderByteCode);

                // Create InputElements
                var inputElements = CreateInputElements();

                // Create the vertex input the layout.
                Layout = new InputLayout(device, ShaderSignature.GetInputSignature(vertexShaderByteCode), inputElements);

                // Release the vertex and pixel shader buffers, since they are no longer needed.
                vertexShaderByteCode.Dispose();
                pixelShaderByteCode.Dispose();

            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// Relases the shader
        /// </summary>
        protected virtual void ShuddownShader()
        {
            // Release the sampler state.
            if (SampleState != null)
            {
                SampleState.Dispose();
                SampleState = null;
            }

            // Release the matrix constant buffer.
            if (ConstantMatrixBuffer != null)
            {
                ConstantMatrixBuffer.Dispose();
                ConstantMatrixBuffer = null;
            }

            // Release the layout.
            if (Layout != null)
            {
                Layout.Dispose();
                Layout = null;
            }

            // Release the pixel shader.
            if (PixelShader != null)
            {
                PixelShader.Dispose();
                PixelShader = null;
            }

            // Release the vertex shader.
            if (VertexShader != null)
            {
                VertexShader.Dispose();
                VertexShader = null;
            }

        }

        #endregion


        /// <summary>
        /// Renders the indexed mesh
        /// </summary>
        protected void RenderIndexed(DeviceContext deviceContext, int indexCount)
        {
            // Set the vertex input layout.
            deviceContext.InputAssembler.InputLayout = Layout;

            // Set the vertex and pixel shaders that will be used to render this triangle.
            deviceContext.VertexShader.Set(VertexShader);
            deviceContext.PixelShader.Set(PixelShader);

            // Set the sampler state in the pixel shader.
            deviceContext.PixelShader.SetSampler(0, SampleState);

            // invoke rendering
            deviceContext.DrawIndexed(indexCount, 0, 0);
        }

        /// <summary>
        /// Renders unindexed geometry primitives
        /// </summary>
        /// <param name="deviceContext"></param>
        /// <param name="indexCount"></param>
        protected void RenderUnindexed(DeviceContext deviceContext, int vertexCount)
        {
            // Set the vertex input layout.
            deviceContext.InputAssembler.InputLayout = Layout;

            // Set the vertex and pixel shaders that will be used to render this triangle.
            deviceContext.VertexShader.Set(VertexShader);
            deviceContext.PixelShader.Set(PixelShader);

            // invoke rendering
            deviceContext.Draw(vertexCount, 0);
        }
    }
}
